﻿using System;
using System.Net.Mime;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;
using System.Xml.Schema;
using System.Xml;
using Argos.Data.Model;

namespace Argos.Activities.MessageDelivery
{
	/// <summary>
	/// Represents an alert message that is sent by message delivery activities to recipients.
	/// </summary>
	[XmlRoot("message", Namespace = AlertMessage.XmlNamespace)]
	public sealed class AlertMessage : IXmlSerializable
	{

		#region #ctor: public AlertMessage()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public AlertMessage()
		{
			this.BodyContentType = MediaTypeNames.Text.Plain;
			this.CreatedDate = DateTime.UtcNow;
			this.RawData = new List<object>();
		}

		#endregion


		internal const string XmlNamespace = "http://develore.com/schemas/2008/06/argos/messagedelivery/alertmessage.xsd";


		/// <summary>
		/// Sets or returns the type of alert.
		/// </summary>
		public AlertTypes AlertType { get; set; }

		/// <summary>
		/// Sets or returns the body of the message.
		/// </summary>
		public string Body { get; set; }

		/// <summary>
		/// Sets or returns the content type of the body.
		/// </summary>
		public string BodyContentType { get; set; }

		/// <summary>
		/// Returns a list containing the raw data that the <see cref="Body"/>
		/// was created from. This allows message delivery activities to format
		/// the body in any way they like.
		/// </summary>
		public List<object> RawData { get; private set; }

		/// <summary>
		/// Sets or returns the address of the recipient.
		/// </summary>
		public string RecipientAddress { get; set; }

		/// <summary>
		/// Sets or returns the name of the recipient. This is usually also
		/// used as display name for the recipient.
		/// </summary>
		public string RecipientName { get; set; }

		/// <summary>
		/// Returns the date and time when the message was originally created.
		/// </summary>
		public DateTime CreatedDate { get; private set; }

		/// <summary>
		/// Sets or returns the subject of the message.
		/// </summary>
		public string Subject { get; set; }

		/// <summary>
		/// Sets or returns the source target this alert message was delivered from.
		/// </summary>
		public Target Target { get; set; }



		#region public static AlertMessage FromXmlString(string xmlString)

		/// <summary>
		/// Deserializes the given XML string and returns an alert message object.
		/// </summary>
		/// <param name="xmlString">The XML string to deserialize.</param>
		public static AlertMessage FromXmlString(string xmlString)
		{
			XmlSerializer ser = new XmlSerializer(typeof(AlertMessage));

			using (MemoryStream strm = new MemoryStream())
			{
				using (StreamWriter writer = new StreamWriter(strm))
				{
					writer.Write(xmlString);
					writer.Flush();
					strm.Position = 0;

					return (AlertMessage)ser.Deserialize(strm);
				}
			}
		}

		#endregion

		#region public string ToXmlString()

		/// <summary>
		/// Serializes the current alert message to an XML string.
		/// </summary>
		public string ToXmlString()
		{
			XmlSerializer ser = new XmlSerializer(typeof(AlertMessage));
			using (MemoryStream strm = new MemoryStream())
			{
				ser.Serialize(strm, this);
				strm.Position = 0;

				using (StreamReader reader = new StreamReader(strm))
				{
					return reader.ReadToEnd();
				}
			}
		}

		#endregion



		public static AlertMessage ReadXml(Stream stream)
		{
			XmlSerializer ser = new XmlSerializer(typeof(AlertMessage));
			return (AlertMessage)ser.Deserialize(stream);
		}

		public static AlertMessage ReadXml(FileInfo sourceFile)
		{
			using (FileStream strm = new FileStream(sourceFile.FullName, FileMode.Open, FileAccess.Read))
			{
				return ReadXml(strm);
			}
		}



		#region public XmlSchema GetSchema()

		/// <summary>
		/// Not implemented.
		/// </summary>
		public XmlSchema GetSchema()
		{
			return null;
		}

		#endregion
		
		#region public void ReadXml(XmlReader reader)

		/// <summary>
		/// Reads the data stored in the given reader and popluates the current
		/// alert message from that data.
		/// </summary>
		/// <param name="reader">The reader to read from.</param>
		public void ReadXml(XmlReader reader)
		{

			if (reader.NamespaceURI != AlertMessage.XmlNamespace)
				throw new InvalidOperationException(string.Format("Unrecognized namespace: '{0}'.", reader.NamespaceURI));

			if (reader.LocalName != "message")
				throw new InvalidOperationException(string.Format("Unrecognized element name: '{0}'.", reader.LocalName));


			reader.ReadStartElement();

			while (reader.NodeType == XmlNodeType.Element)
			{
				switch (reader.LocalName)
				{
					case "alertType":
						this.AlertType = (AlertTypes)Enum.Parse(typeof(AlertTypes), reader.ReadElementContentAsString());
						break;

					case "body":
						this.Body = reader.ReadElementContentAsString();
						break;

					case "bodyContentType":
						this.BodyContentType = reader.ReadElementContentAsString();
						break;

					case "createdDate":
						this.CreatedDate = reader.ReadExactDateTimeElement();
						break;

					case "rawData":
						this.RawData.Clear();
						reader.ReadElementContent(this.RawData, "item");
						break;

					case "recipientAddress":
						this.RecipientAddress = reader.ReadElementContentAsString();
						break;

					case "recipientName":
						this.RecipientName = reader.ReadElementContentAsString();
						break;

					case "subject":
						this.Subject = reader.ReadElementContentAsString();
						break;

					case "target":
						this.Target = new Target();
						this.Target.ReadXml(reader);
						break;

					default:
						throw new InvalidOperationException(string.Format("Unrecognized element: '{0}'.", reader.LocalName));
				}
			}

		}

		#endregion
		
		#region public void WriteXml(XmlWriter writer)

		/// <summary>
		/// Writes the data stored in the current alert message instance to
		/// the given writer.
		/// </summary>
		/// <param name="writer">The writer to write to.</param>
		public void WriteXml(XmlWriter writer)
		{
			writer.WriteElementString("alertType", this.AlertType.ToString());
			writer.WriteElementString("body", this.Body);
			writer.WriteElementString("bodyContentType", this.BodyContentType);
			writer.WriteExactDateTimeElement(this.CreatedDate, "createdDate");
			if (this.RawData.Count > 0)
			{
				writer.WriteListElement(this.RawData, "rawData", "item");
			}
			writer.WriteElementString("recipientAddress", this.RecipientAddress);
			writer.WriteElementString("recipientName", this.RecipientName);
			writer.WriteElementString("subject", this.Subject);

			if (null != this.Target)
			{
				writer.WriteStartElement("target", Target.XmlNamespace);
				this.Target.WriteXml(writer);
				writer.WriteEndElement();
			}
		}

		#endregion

		#region public void WriteXml(Stream stream)

		/// <summary>
		/// Writes the contents of the current message to the given stream in XML format.
		/// </summary>
		/// <param name="stream">The stream to write to.</param>
		public void WriteXml(Stream stream)
		{
			XmlSerializer ser = new XmlSerializer(typeof(AlertMessage));
			ser.Serialize(stream, this);
		}

		#endregion

		#region public void WriteXml(FileInfo targetFile)

		/// <summary>
		/// Writes the contents of the current message to an XML file.
		/// </summary>
		/// <param name="targetFile">The full path to the file to write to.</param>
		public void WriteXml(FileInfo targetFile)
		{
			using (FileStream strm = new FileStream(targetFile.FullName, FileMode.Create, FileAccess.Write))
			{
				this.WriteXml(strm);
			}
		}

		#endregion

	}
}
